/* Copyright (c) 2011 Jordan Gray, MIT; see COPYRIGHT */


#ifndef STRING_H_
#define STRING_H_

#include <iosfwd>
#include <cstring>

namespace ch11ex02 {

class String {
public:
  class Cref;
  class Range {};
  //explicit String(const char * p_str);
  String();
  String(const char * p_str);
  String(const String& str);
  String& operator=(const char *);
  String& operator=(const String&);
  ~String();
  friend void swap(String& first, String& second);

  String& operator+=(const String&);
  String& operator+=(const char*);

  friend String operator+(const String&, const String&);
  friend String operator+(const String&, const char*);

  friend std::ostream& operator<<(std::ostream&, const String&);
  friend std::istream& operator>>(std::istream&, String&);

  friend bool operator==(const String& x, const char* s) {
    return strcmp(x.p_rep_->p_buf_, s) == 0;
  }
  friend bool operator==(const String& x, const String& y) {
    return strcmp(x.p_rep_->p_buf_, y.p_rep_->p_buf_) == 0;
  }
  friend bool operator!=(const String& x, const char* s) {
    return strcmp(x.p_rep_->p_buf_, s) != 0;
  }
  friend bool operator!=(const String& x, const String& y) {
    return strcmp(x.p_rep_->p_buf_, y.p_rep_->p_buf_) != 0;
  }

  void check(int index) const {
    if(index<0 || index > p_rep_->num_char_) throw Range();
  }
  char read(int index) const { // unchecked index, use [] for checked
    return p_rep_->p_buf_[index];
  }
  void write(int index, char c) { // also unchecked index, use [] for checked
    p_rep_ = p_rep_->get_own_copy(); p_rep_->p_buf_[index] = c;
  }
  Cref operator[](int index) {
    check(index);
    return Cref(*this,index);
  }
  char operator[](int index) const {
    check(index);
    return p_rep_->p_buf_[index];
  }
  int size() const { return p_rep_->num_char_; }

  class Cref {
    public:
      operator char() const { return str_.read(index_); }
      void operator=(char c) { str_.write(index_,c); }
    private:
      friend class String;
      String& str_;
      int index_;
      Cref(String& str, int index) : str_(str), index_(index) {}
    };

private:
  class Srep;
  Srep* p_rep_;

  String(Srep * p_rep) : p_rep_(p_rep) {}

  struct Srep {
    char * p_buf_; // pointer to buffer holding string
    int num_char_; // number of characters (one less than length of buffer)
    int num_ref_;  // reference count
    Srep(int num_char, const char * p_buf) {
      num_ref_ = 1;
      num_char_ = num_char;
      p_buf_ = new char[num_char_+1]; // add 1 for terminator
      strncpy(p_buf_, p_buf, num_char_);
      p_buf_[num_char_] = '\0';
    }
    // "addition" constructor
    Srep(int num_char_a, const char * p_buf_a, int num_char_b, const char * p_buf_b) {
      num_ref_ = 1;
      num_char_ = num_char_a + num_char_b;
      p_buf_ = add(num_char_a, p_buf_a, num_char_b, p_buf_b);
    }
    ~Srep() { delete[] p_buf_; }
    Srep* get_own_copy() {
      if(num_char_==1) return this;
      --num_ref_;
      return new Srep(num_char_, p_buf_);
    }
    void assign(int num_char, const char* p_buf) {
      if(num_char_ != num_char) {
        delete[] p_buf_;
        num_char_ = num_char;
        p_buf_ = new char[num_char_+1];
      }
      strncpy(p_buf_, p_buf, num_char_);
      p_buf_[num_char_] = '\0';
    }
    void extend(int num_char, const char* p_buf) {
      char * p_buf_ab = add(num_char_, p_buf_, num_char, p_buf);
      delete[] p_buf_; // delete old, short copy
      p_buf_ = p_buf_ab;
      num_char_ += num_char;
    }
    // allocate memory and copy a and b into resulting space, return reference to new array
    static char * add(int num_char_a, const char* p_buf_a, int num_char_b, const char* p_buf_b) {
      int num_char_extended = num_char_a + num_char_b;
      char * p_buf_ab = new char[num_char_extended + 1]; // add 1 for terminator
      strncpy(p_buf_ab, p_buf_a, num_char_a);
      strncpy(p_buf_ab+num_char_a, p_buf_b, num_char_b);
      p_buf_ab[num_char_extended] = '\0';
      return p_buf_ab;
    }
    friend std::ostream& operator<<(std::ostream& out, Srep& str);
  private:
    Srep(const Srep&);
    Srep& operator=(const Srep&);
  };

};

} // namespace ch11ex02

#endif /* STRING_H_ */
